import {
  createEffect,
  createMemo,
  createSignal,
  mergeProps,
  on,
  onCleanup,
  type ParentComponent,
  splitProps,
} from "solid-js";
import type {
  SolidModalBodyProps,
  SolidModalCloseButtonProps,
  SolidModalContentProps,
  SolidModalDialogProps,
  SolidModalFooterProps,
  SolidModalHeaderProps,
  SolidModalProps,
  SolidModalTitleProps,
} from "./types";
import { useModal } from "./helper";
import { Dynamic } from "solid-js/web";
import { wrapClasses, wrapOptions } from "@lib/types";
import { Modal } from "bootstrap";
import type { JSX } from "solid-js/jsx-runtime";
import { logger } from "@lib/utils";

const bsModalEvents: { [key: string]: string } = {
  onHide: "hide.bs.modal",
  onHidden: "hidden.bs.modal",
  onHidePrevented: "hidePrevented.bs.modal",
  onShow: "show.bs.modal",
  onShown: "shown.bs.modal",
};

type _EventHandlers = Pick<
  SolidModalProps,
  "onShow" | "onShown" | "onHidden" | "onHide" | "onHidePrevented"
>;

const registerEvents = (
  el?: Element,
  eventHandlers?: Partial<_EventHandlers & any>
) => {
  if (!el || !eventHandlers) {
    return;
  }

  Object.keys(bsModalEvents).forEach((eventName) => {
    if (eventHandlers[eventName]) {
      const bsEventName = bsModalEvents[eventName];
      const handler = eventHandlers[eventName];
      el.addEventListener(bsEventName, handler);
    }
  });
};

const unregisterEvents = (el?: Element, eventHandlers?: any) => {
  if (!el) {
    return;
  }

  Object.keys(bsModalEvents).forEach((eventName) => {
    if (eventHandlers[eventName]) {
      const bsEventName = bsModalEvents[eventName];
      const handler = eventHandlers[eventName];
      el.removeEventListener(bsEventName, handler);
    }
  });
};

const BsModal: ParentComponent<
  SolidModalProps & JSX.HTMLAttributes<HTMLDivElement>
> = (p) => {
  const [local, props] = splitProps(mergeProps({}, p), [
    "children",
    "ref",
    "$ServerOnly",
    "fade",
    "onShow",
    "onShown",
    "onHide",
    "onHidden",
    "onHidePrevented",
    "classList",
    "onRef",
    "options",
    "size",
    "scrollable",
    "centered",
    "fullscreen",
  ]);

  const _bs_options = mergeProps(
    { backdrop: true, focus: true, Keyboard: true },
    local.options
  );

  const modalClassList = createMemo(() => {
    return {
      [`modal-${local.size}`]: local.size !== undefined,
      ...local.classList,
      fade: local.fade,
    };
  });

  const [ref, setRef] = createSignal<HTMLDivElement | undefined>(undefined);
  createEffect(
    on(ref, (el) => {
      registerEvents(el, local);
    })
  );

  const modalInstance = createMemo(() => {
    const e = ref();

    if (e !== undefined && e !== null) {
      return new Modal(e, local.options);
    }

    return undefined;
  });

  createEffect(
    on(modalInstance, (instnace) => {
      logger.log("modal created", instnace);
      local.onRef?.(instnace);
    })
  );

  onCleanup(() => {
    modalInstance()?.dispose();

    const el = ref();

    unregisterEvents(el, local);
  });

  return (
    <div
      ref={setRef}
      class="modal"
      classList={modalClassList()}
      tabIndex={-1}
      {...props}
    >
      <BsModalDialog scrollable={local.scrollable} centered={local.centered} fullscreen={local.fullscreen}>
        <BsModalContent>{local.children}</BsModalContent>
      </BsModalDialog>
    </div>
  );
};

const BsModalDialog: ParentComponent<SolidModalDialogProps> = (p) => {
  const [local, props] = splitProps(p, [
    "children",
    "classList",
    "ref",
    "scrollable",
    "centered",
    "fullscreen",
  ]);

  const classList = createMemo(() => {
    return {
      "modal-dialog-scrollable": local.scrollable,
      "modal-dialog-centered": local.centered,
      [`modal-fullscreen${
        typeof local.fullscreen === "boolean"
          ? ""
          : "-" + local.fullscreen + "-down"
      }`]: local.fullscreen !== undefined,
      ...local.classList,
    };
  });

  return (
    <div
      class="modal-dialog"
      ref={local.ref}
      classList={classList()}
      {...props}
    >
      {local.children}
    </div>
  );
};
const BsModalContent: ParentComponent<SolidModalContentProps> = (p) => (
  <div class="modal-content" {...p}></div>
);
const BsModalTitle: ParentComponent<SolidModalTitleProps> = (p) => {
  const [local, props] = splitProps(
    mergeProps({ as: "h1", class: "modal-title" }, p),
    ["as", "$ServerOnly", "classes", "classList"]
  );

  const classList = createMemo(() => {
    return {
      ...local.classList,
      ...wrapClasses(local.classes),
    };
  });

  return (
    <Dynamic component={local.as} {...props} classList={classList()}></Dynamic>
  );
};

const BsModalHeader: ParentComponent<SolidModalHeaderProps> = (p) => {
  const [local, props] = splitProps(p, ["children", "ref", "$ServerOnly"]);

  return (
    <div class="modal-header" {...props}>
      {local.children}
    </div>
  );
};

const BsModalBody: ParentComponent<SolidModalBodyProps> = (p) => {
  const [local, props] = splitProps(p, ["children", "ref", "$ServerOnly"]);

  return (
    <div class="modal-body" {...props}>
      {local.children}
    </div>
  );
};

const BsModalFooter: ParentComponent<SolidModalFooterProps> = (p) => (
  <div class="modal-footer" {...p}></div>
);
const BsModalCloseButton: ParentComponent<SolidModalCloseButtonProps> = (p) => {
  return (
    <button
      type="button"
      class="btn-close"
      data-bs-dismiss="modal"
      aria-label="Close"
      {...p}
    ></button>
  );
};

type CompoundedComponent = typeof BsModal & {
  useModal: typeof useModal;
  Content: typeof BsModalContent;
  Dialog: typeof BsModalDialog;
  Title: typeof BsModalTitle;
  Header: typeof BsModalHeader;
  Body: typeof BsModalBody;
  Footer: typeof BsModalFooter;
  CloseButton: typeof BsModalCloseButton;
};

const SolidModal = BsModal as CompoundedComponent;

SolidModal.useModal = useModal;
SolidModal.Content = BsModalContent;
SolidModal.Dialog = BsModalDialog;
SolidModal.Header = BsModalHeader;
SolidModal.Body = BsModalBody;
SolidModal.Footer = BsModalFooter;
SolidModal.CloseButton = BsModalCloseButton;
SolidModal.Title = BsModalTitle;

export default SolidModal;
